﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using NClass.Core;
using NClass.TestHelper;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;

namespace NClass.Test.Core.Relationships
{
	[TestClass]
	public class AssociationRelationshipTest
	{
		private class DeserializeTestSet
		{
			private static int counter = -1;
			internal DeserializeTestSet(string xml, Func<AssociationRelationship, bool> v)
			{
				counter++;
				TestSetNumber = counter;
				Element = string.IsNullOrWhiteSpace(xml) ? null : LoadElement(xml);
				Validator = v;
			}

			internal int TestSetNumber { get; private set; }
			internal XmlElement Element { get; private set; }
			internal Func<AssociationRelationship, bool> Validator { get; private set; }

			private static XmlElement LoadElement(string xml)
			{
				var document = new XmlDocument();
				document.LoadXml(string.Format("<{0} xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">{1}</{0}>", "Node", xml));
				return document.DocumentElement;
			}
		}

		private static readonly string _testSet1 = "<Label>Test Set #1</Label><Direction></Direction>";
		private static readonly string _testSet2 = "<Direction>Unidirectional</Direction><IsAggregation>True</IsAggregation>" +
			"<IsComposition>True</IsComposition><AssociationType>Aggregation</AssociationType>";
		private static readonly string _testSet3 = "<Direction>SourceDestination</Direction><IsAggregation>False</IsAggregation>" +
			"<IsComposition>False</IsComposition><AssociationType>Composition</AssociationType>";
		private static readonly string _testSet4 = "<AssociationType>Other</AssociationType>" +
			string.Format("<{0}>{0}</{0}>", "StartRole") +
			string.Format("<{0}>{0}</{0}>", "EndRole") +
			string.Format("<{0}>{0}</{0}>", "StartMultiplicity") +
			string.Format("<{0}>{0}</{0}>", "EndMultiplicity");

		[TestMethod]
		[TestCategoryAttribute("Branch Path Coverage")]
		public void Deserialize()
		{
			// Also must cover the Relationship.Deserialize(XmlNode) method

			// Inputs Type1, Type2, XmlElement
			// Output -> AssociationRelationship
			// Validate Ouptut
			var dataSetCollection = new List<DeserializeTestSet>();

			dataSetCollection.Add(new DeserializeTestSet("", null));
			dataSetCollection.Add(new DeserializeTestSet(_testSet1, (relationship) =>
				{
					// Use Expected becuase Method returns VOID so Actual is NULL
					// Plus it should be modifing a reference to itself
					if (!string.Equals("Test Set #1", relationship.Label, StringComparison.CurrentCulture))
						return false;

					return true;
				}));

			dataSetCollection.Add(new DeserializeTestSet(_testSet2, (relationship) =>
			{
				// Use Expected becuase Method returns VOID so Actual is NULL
				// Plus it should be modifing a reference to itself
				if (!string.Equals(string.Empty, relationship.Label, StringComparison.CurrentCulture))
					return false;

				return true;
			}));

			dataSetCollection.Add(new DeserializeTestSet(_testSet3, (relationship) =>
			{
				// Use Expected becuase Method returns VOID so Actual is NULL
				// Plus it should be modifing a reference to itself
				if (!string.Equals(string.Empty, relationship.Label, StringComparison.CurrentCulture))
					return false;

				return true;
			}));

			dataSetCollection.Add(new DeserializeTestSet(_testSet4, (relationship) =>
			{
				// Use Expected becuase Method returns VOID so Actual is NULL
				// Plus it should be modifing a reference to itself
				if (!string.Equals(string.Empty, relationship.Label, StringComparison.CurrentCulture))
					return false;

				return true;
			}));


			Language language = null;
			var model = Resolver.CreateModel(Resolver.Language.CSharp, out language);

			AssociationRelationship associationRelationship = null;

			var methodInfo = (typeof(AssociationRelationship)).GetMethod("Deserialize",
				BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

			// Execute Test
			foreach (var dataSet in dataSetCollection)
			{
				var type1 = model.AddClass();
				type1.Name = string.Format("TestSet{0}A", dataSet.TestSetNumber);

				var type2 = model.AddClass();
				type2.Name = string.Format("TestSet{0}B", dataSet.TestSetNumber);

				associationRelationship = model.AddAssociation(type1, type2);

				// Deserialize the XmlElement into the association
				var vectorMessage = string.Format("Test Set #{0}. ", dataSet.TestSetNumber);
				var errorMessage = string.Empty;
				object result;

				try
				{
					methodInfo.Invoke(associationRelationship, new object[] { dataSet.Element });

					if (dataSet.Validator == null)
						Assert.Fail("Test was expected to throw an exception");
					else if (!dataSet.Validator(associationRelationship))
						Assert.Fail("Validation Function Failed for the Test Set #" + dataSet.TestSetNumber);
				}
				catch (TargetInvocationException targetEx)
				{
					if (targetEx.InnerException is ArgumentNullException &&
						dataSet.Validator == null)
						continue;
					
					Assert.Fail(vectorMessage + targetEx.Message + " " + targetEx.InnerException.Message);
				}
				catch (Exception ex)
				{
					Assert.Fail(vectorMessage + ex.Message);
				}
			}

		}
	}
}
